package com.xwaf.platform.system.provider.serviceimpl.pay;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.codehaus.xfire.util.Base64;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.xwaf.platform.common.utils.DateHelper;
import com.xwaf.platform.common.utils.XmlUtil;
import com.xwaf.platform.common.utils.http.HttpUtil;
import com.xwaf.platform.system.api.entity.pay.wx.WeiXinPayResData;
import com.xwaf.platform.system.api.entity.pay.wx.WeiXinSignature;
import com.xwaf.platform.system.api.entity.pay.wx.WeixinPayReqData;
import com.xwaf.platform.system.api.entity.three.wx.WechatApplet;
import com.xwaf.platform.system.api.request.three.WeiXinProductRequest;
import com.xwaf.platform.system.api.service.pay.WeiXinService;

@Service(value = "weiXinService")
public class WeiXinServiceImpl implements WeiXinService {
	private static Logger log = LoggerFactory.getLogger(WeiXinServiceImpl.class);

	// 表示请求器是否已经做了初始化工作
	private boolean hasInit = false;

	// 连接超时时间，默认5秒
	private int socketTimeout = 5000;

	// 传输超时时间，默认30秒
	private int connectTimeout = 30000;

	// 请求器的配置
	private RequestConfig requestConfig;

	// HTTP请求器
	private CloseableHttpClient httpClient;

	private byte[] certData;

	public InputStream getCertStream() {
		try {
			InputStream certStream = Thread.currentThread().getContextClassLoader()
					.getResourceAsStream("apiclient_cert.p12");
			this.certData = IOUtils.toByteArray(certStream);
			certStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
		return certBis;
	}

	@Override
	public String sendPost(String api_url, Object xmlObj, String mchId) throws Exception {
		if (!hasInit)
			init(mchId);
		String result = null;

		HttpPost httpPost = new HttpPost(api_url);
		// 解决XStream对出现双下划线的bug
		XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
		log.info(" 请求微信服务的 xmlObj 服务类 =" + xmlObj);
		// 将要提交给API的数据对象转换成XML格式数据Post给API
		String postDataXML = xStreamForRequestPostData.toXML(xmlObj);

		log.info("API，POST过去的数据是：" + postDataXML);

		// 得指明使用UTF-8编码，否则到API服务器XML的中文不能被成功识别
		StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
		httpPost.addHeader("Content-Type", "text/xml");
		httpPost.setEntity(postEntity);

		// 设置请求器的配置
		httpPost.setConfig(requestConfig);

		log.info("executing request" + httpPost.getRequestLine());
		try {
			HttpResponse response = httpClient.execute(httpPost);
			HttpEntity entity = response.getEntity();
			result = EntityUtils.toString(entity, "UTF-8");
			log.info("API，POST响应的的数据是=====》：" + result);
		} catch (ConnectTimeoutException | SocketTimeoutException e) {
			log.info("http get throw ConnectTimeOut(wait time out)", e);
			log.error("http get throw ConnectTimeOut(wait time out)", e);
		} catch (Exception e) {
			log.info("http get throw Exception", e);
			log.error("http get throw Exception", e);

		} finally {
			httpPost.abort();
		}
		return result;
	}

	/**
	 * 初始化操作,为了使用微信证书,做特殊处理
	 */
	private void init(String mchId) throws Exception {
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		keyStore.load(getCertStream(), mchId.toCharArray());
		// Trust own CA and all self-signed certs
		SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
		// Allow TLSv1 model only
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
				SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
		httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

		// 根据默认超时限制初始化requestConfig
		requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout)
				.build();
		log.info(" 请求微信服务的 hasInit 服务类 =" + hasInit);
		hasInit = true;
	}

	@Override
	public Map<String, String> appPayRequest(WeiXinProductRequest order) {
		Map<String, String> res = new HashMap<String, String>();
		Date gmtCreate = new Date();
		Date orderInvalidTime = DateUtils.addMinutes(gmtCreate, 60);
		String detail = "商品充值", openId = null;
		// 构造请求微信支付的对象
		WeixinPayReqData payReqData = new WeixinPayReqData(order.getAppId(), order.getMchId(), "商品充值", // body
				detail, // detail
				null, // attach 标记
				order.getOrderNo(), // out_trade_no 商户订单号
				null, // FeeType 默认是人民币
				1, // totolfee 单位是分
				null, // deviceInfo 暂时不需要
				"127.0.0.1", // 客户端 IP
				DateFormatUtils.format(gmtCreate, "yyyyMMddHHmmss"), // 订单创建时间
				DateFormatUtils.format(orderInvalidTime, "yyyyMMddHHmmss"), // 订单失效时间
				null, // 商品标记,暂时不用,后续优惠券之类的可以考虑使用
				"http://www.baidu.com", // 异步回调地址
				"APP", //
				null, //
				"no_credit", // 不支持信用卡
				openId, order.getApiKey());
		// app的微信请求
		try {
			String payResultString = this.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", payReqData,
					order.getMchId());
			// 转换为对应的对象并处理
			// 将从API返回的XML数据映射到Java对象
			WeiXinPayResData payResData = (WeiXinPayResData) XmlUtil.getObjectFromXMLForWechat(payResultString,
					WeiXinPayResData.class);
			res.put("status", "true");
			res.put("message", "微信签名成功");
			// 下单失败
			if (payResData == null || payResData.getReturn_code() == null
					|| StringUtils.equals(payResData.getReturn_code(), "FAIL")
					|| StringUtils.equals(payResData.getResult_code(), "FAIL")) {
				log.info("prepay wechat error,please check params and try again");
				res.put("code", "WECHAT_PREPAY_ERROR");// 微信支付失败,请稍后重试
				res.put("message", "微信支付失败,请稍后重试" + "[" + payResData.getReturn_msg() + "]");
				return res;
			}

			// 下单成功,则封装需要返回的参数
			// 设置固定值
			res.put("appId", order.getAppId());
			res.put("partnerId", order.getMchId());
			// 随机数
			res.put("nonceStr", payReqData.getNonce_str());
			// prepayID
			res.put("prepayId", payResData.getPrepay_id());

			// timestamp,最后获取
			String timeStampInSeconds = DateHelper.getTimeStampInSeconds();
			res.put("timestamp", timeStampInSeconds);
			// 构造签名
			res.put("sign", WeiXinSignature.genWechatPaySign(order.getAppId(), payReqData.getNonce_str(),
					order.getMchId(), payResData.getPrepay_id(), timeStampInSeconds, order.getApiKey()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return res;
	}

	/**
	 * 获取小程序 openId,session_key <br>
	 * 可获取openid及session_key,其实这里openid不需要获取<br>
	 * encryptedData解密后包含openid
	 */
	@Override
	public JSONObject getOpenId(String js_code) throws IOException {
		// 官方接口，需要自己提供appid，secret和js_code
		Map<String, String> resultMap = new HashMap<String, String>();
		String requestUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WechatApplet.appId + "&secret="
				+ WechatApplet.secret + "&js_code=" + js_code + "&grant_type=authorization_code";
		// HttpRequestor是一个网络请求工具类，贴在了下面
		String str = new HttpUtil().doRequest(requestUrl, null, HttpUtil.GET);
		JSONObject getOpenId = new JSONObject(str);
		System.out.println("jscode2session method response==========" + getOpenId);
		return getOpenId;
	}

	@Override
	public JSONObject getUserInfo(String encryptedData, String session_key, String iv) {
		// 被加密的数据
		byte[] dataByte = Base64.decode(encryptedData);
		// 加密秘钥
		byte[] keyByte = Base64.decode(session_key);
		// 偏移量
		byte[] ivByte = Base64.decode(iv);
		try {
			// 如果密钥不足16位，那么就补足. 这个if 中的内容很重要
			int base = 16;
			if (keyByte.length % base != 0) {
				int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
				byte[] temp = new byte[groups * base];
				Arrays.fill(temp, (byte) 0);
				System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
				keyByte = temp;
			}
			// 初始化
			Security.addProvider(new BouncyCastleProvider());
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
			SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
			AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
			parameters.init(new IvParameterSpec(ivByte));
			cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
			byte[] resultByte = cipher.doFinal(dataByte);
			if (null != resultByte && resultByte.length > 0) {
				String result = new String(resultByte, "UTF-8");
				return new JSONObject(result);
			}
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidParameterSpecException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		}
		return null;
	}
}
